﻿using DotNetExtensions.AspNet;
using Habi.Solver.Helpers;
using Habi.Solver.Models.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Habi.Solver.Common
{
    public class TaskFtpDownloader : Singleton<TaskFtpDownloader>
    {
        private readonly string DOWNLOADROOT;
        private TaskFtpDownloader()
        {
            DOWNLOADROOT = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Downloads");
            if (!Directory.Exists(DOWNLOADROOT)) Directory.CreateDirectory(DOWNLOADROOT);
        }
        

        private readonly Dictionary<long, ResourceTaskModel> __TASK__ = new Dictionary<long, ResourceTaskModel>();

        private readonly Dictionary<long, CancellationTokenSource> Cancellation = new Dictionary<long, CancellationTokenSource>();

        public event TransferTaskEvent OnTaskCompleted;
        public event TransferTaskEvent OnTaskProgressChanged;

        public string BaseUrl { get; set; }
        public string User { get; set; }
        public string Password { get; set; }

        public void Start(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (__TASK__.ContainsKey(task.Id)) return;

                __TASK__.Add(task.Id, task);

                HttpRequestStart(task);
            }
        }

        private void HttpRequestStart(ResourceTaskModel task)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            Cancellation.Add(task.Id, source);

            Task.Factory.StartNew((args) =>
            {
                try
                {
                    task.Status = (int)ResourceTaskStatus.Downloading;
                    OnTaskProgressChanged?.Invoke(new TaskEventArgs(task));

                    CancellationToken cancel = (CancellationToken)args;

                    long position = 0x0;
                    // use temp file for download
                    string filename = Path.Combine(DOWNLOADROOT, task.Id + ".temp");

                    // long | 8byt 
                    if (File.Exists(filename))
                    {
                        using (Stream stream = File.OpenRead(filename))
                        {
                            position = stream.Length;
                        }
                    }

                    // chunk size 4M
                    // 1G - 100
                    // 10 - 1000

                    int chunk = 4 * 1024 * 1024;

                    long total = position;
                    string url = Path.Combine(BaseUrl, filename);
                    var request = (FtpWebRequest)WebRequest.Create(url);
                    request.Method = WebRequestMethods.Ftp.UploadFile;

                    request.UseBinary = true;
                    request.KeepAlive = false;
                    request.UsePassive = false;

                    if (!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Password))
                    {
                        request.Credentials = new NetworkCredential(User, Password);
                        request.UsePassive = true;
                    }
                    var response = request.GetResponse();
                    
                    using (Stream stream = response.GetResponseStream())
                    {
                        
                        if (!stream.CanRead) throw new Exception("stream can't read.");
                        task.Size = response.ContentLength;
                        using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
                        {
                            fs.Position = position;
                            byte[] byts = new byte[chunk];
                            int len = 0;

                            do
                            {
                                if (cancel.IsCancellationRequested) break;

                                len = stream.Read(byts, 0, byts.Length);
                                fs.Write(byts, 0, len);
                                fs.Flush();
                                total += len;
                                double progress = (total + 0.1) / (task.Size + 0.1);
                                task.Progress = Math.Round(progress, 2);
                                OnTaskProgressChanged?.Invoke(new TaskEventArgs(task));
                            } while (len > 0);
                        }
                    }

                    if (total == task.Size)
                    {
                        task.Progress = 1;

                        string dest = Path.Combine(DOWNLOADROOT, task.Name + "." + task.Extension);
                        if (string.IsNullOrEmpty(task.Extension) || string.IsNullOrEmpty(task.Name))
                        {
                            string[] disp = response.Headers.GetValues("Content-Disposition");
                            if (disp != null && disp.Length > 0)
                            {
                                dest = Path.Combine(DOWNLOADROOT, disp[0]);
                            }
                            else
                            {
                                //http://localhost/%E9%BB%84%E5%8D%AB%E5%B9%B3-21-09%E6%9C%88%E6%8A%A5.xlsx
                                string name = PathHelper.GetNameFromUrl(task.Url);
                                if (!string.IsNullOrEmpty(name)) dest = Path.Combine(DOWNLOADROOT, name);
                            }
                        }

                        Cancellation.Remove(task.Id);
                        task.Status = (int)ResourceTaskStatus.Downloaded;

                        OnTaskCompleted?.Invoke(new TaskEventArgs(task, filename, dest, true));
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Task download error, msg: {0}", ex.Message);
                    task.Status = (int)ResourceTaskStatus.Error;
                    OnTaskCompleted?.Invoke(new TaskEventArgs(task));
                }

            }, source.Token, source.Token);
        }

        public void Pause(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (!Cancellation.ContainsKey(task.Id)) return;
                Cancellation[task.Id].Cancel();
            }
        }

        public void Delete(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (!Cancellation.ContainsKey(task.Id)) return;
                Cancellation[task.Id].Cancel();
                __TASK__.Remove(task.Id);
            }
        }
    }
}
